#include <crt_thread.h>
#include <crt_memory.h>

#if (TARGET_OS == OS_WINDOWS)

struct thread_ctx {
	void(*entry)(void* arg);
	void* arg;
	_thread_t self;
};

static UINT __stdcall _thread_cb(void* arg) {

	struct thread_ctx *ctx_p;
	struct thread_ctx ctx;
	ctx_p = arg;
	ctx = *ctx_p;
	heap_free(ctx_p);
	ctx.entry(ctx.arg);

	return 0;
}

 int _thread_create(_thread_t *tid, os_thread_cb entry, void *arg) {

	struct thread_ctx* ctx;
	int err;
	HANDLE thread;

	ctx = heap_malloc(sizeof(*ctx));
	if (ctx == NULL)
		return -1;

	ctx->entry = entry;
	ctx->arg = arg;

	/* Create the thread in suspended state so we have a chance to pass
	* its own creation handle to it */
	thread = (HANDLE)CreateThread(NULL,
		0,
		_thread_cb,
		ctx,
		CREATE_SUSPENDED,
		NULL);
	if (thread == NULL) {
		err = errno;
		heap_free(ctx);
	}
	else {
		err = 0;
		*tid = thread;
		ctx->self = thread;
		ResumeThread(thread);
	}

	if (!err)
		return 0;
	return -1;
}



int _thread_join(_thread_t *tid) {
	if (WaitForSingleObject(*tid, INFINITE))
		return -1;
	else {
		CloseHandle(*tid);
		*tid = 0;
		MemoryBarrier();  /* For feature parity with pthread_join(). */
		return 0;
	}
}

int _thread_equal(const _thread_t* t1, const _thread_t* t2) {
	return *t1 == *t2;
}


#elif (TARGET_OS == OS_POSIX)

static size_t thread_stack_size(void) {
	struct rlimit lim;

	if (getrlimit(RLIMIT_STACK, &lim)) {
		abort();
	}

	if (lim.rlim_cur != RLIM_INFINITY) {
		lim.rlim_cur -= lim.rlim_cur % (rlim_t)getpagesize();
		if (lim.rlim_cur >= PTHREAD_STACK_MIN)
			return lim.rlim_cur;
	}
	return 0;
}


int _thread_create(_thread_t *tid, os_thread_cb entry, void *arg) {
	int err;
	size_t stack_size;
	pthread_attr_t* attr;
	pthread_attr_t attr_storage;

	attr = NULL;
	stack_size = thread_stack_size();

	if (stack_size > 0) {
		attr = &attr_storage;

		if (pthread_attr_init(attr))
			abort();

		if (pthread_attr_setstacksize(attr, stack_size))
			abort();
	}

	err = pthread_create(tid, attr, (void*(*)(void*)) entry, arg);

	if (attr != NULL)
		pthread_attr_destroy(attr);

	return err;
}
int	_thread_join(_thread_t *tid) {
	return pthread_join(*tid, NULL);
}

int	_thread_equal(const _thread_t* t1, const _thread_t* t2) {
	return pthread_equal(*t1, *t2);
}

#elif (TARGET_OS == OS_DARWIN)

static size_t thread_stack_size(void) {
    struct rlimit lim;

    if (getrlimit(RLIMIT_STACK, &lim)) {
        abort();
    }

    if (lim.rlim_cur != RLIM_INFINITY) {
        lim.rlim_cur -= lim.rlim_cur % (rlim_t)getpagesize();
        if (lim.rlim_cur >= PTHREAD_STACK_MIN)
            return lim.rlim_cur;
    }
    return 0;
}


int _thread_create(_thread_t *tid, os_thread_cb entry, void *arg) {
    int err;
    size_t stack_size;
    pthread_attr_t* attr;
    pthread_attr_t attr_storage;

    attr = NULL;
    stack_size = thread_stack_size();

    if (stack_size > 0) {
        attr = &attr_storage;

        if (pthread_attr_init(attr))
            abort();

        if (pthread_attr_setstacksize(attr, stack_size))
            abort();
    }

    err = pthread_create(tid, attr, (void*(*)(void*)) entry, arg);

    if (attr != NULL)
        pthread_attr_destroy(attr);

    return err;
}
int    _thread_join(_thread_t *tid) {
    return pthread_join(*tid, NULL);
}

int    _thread_equal(const _thread_t* t1, const _thread_t* t2) {
    return pthread_equal(*t1, *t2);
}

#endif



